using System;
using System.Collections;
using System.Data;
using System.Text;

using Mono.Cecil;
using Mono.Cecil.Cil;

using CILFlow;
using CILUtil;

using System.Windows.Forms;

namespace CILDiffLib
{
    public class Visit
    {
        public void CompareFiles(string fileA, string fileB)
        {
            AssemblyDefinition a = LoadFile(fileA);
            AssemblyDefinition b = LoadFile(fileB);
            if (a == null || b == null)
                return;

            Compare(a, b);
        }

        class EntityVersion<E>
        {
            public EntityVersion( E old, E n )
            {
                this.Original = old;
                this.Target = n;
            }
            public E Original;
            public E Target;
        }

        delegate string Name<T>(T t);

        public void Compare(AssemblyDefinition a, AssemblyDefinition b )
        {
            Hashtable htA = CollectTypes(a);
            Hashtable htB = CollectTypes(b);

            Hashtable deletedTypes = new Hashtable();
            Hashtable commonTypes = new Hashtable();
            Hashtable newTypes = new Hashtable();
            
            Name<TypeDefinition> nameFunc = delegate(TypeDefinition tr) { return CodeProperties.GetClassName(tr); };

            CollectCandidateMatches(htA, htB, deletedTypes, commonTypes, newTypes, nameFunc);

            foreach (EntityVersion<TypeDefinition> version in commonTypes.Values)
            {
                //version.Original.Properties
                //version.Original.Fields
                //version.Original.Attributes
                //version.Original.Methods
                CompareMethods(version);
                //version.Original.Constructors
                
                // Properties
                //version.Original.IsAbstract
            }
        }

        Hashtable htTopics = new Hashtable();
        Hashtable htMethodTopics = new Hashtable();

        public Hashtable Topics
        {
            get { return htTopics; }
        }

        public Hashtable MethodTopics
        {
            get { return htMethodTopics; }
        }
       
        private void Emit(MethodDefinition md, string state)
        {
            string name = CodeProperties.GetMethodNameWithParametersTight(md, true);
            name = CodeProperties.GetClassName(md.DeclaringType) + "." + name;
            htTopics[name] = state;
        }

        private void Emit(TypeReference tr,MethodDefinition md, string change )
        {
            string name = CodeProperties.GetMethodNameWithParametersTight(md, true);
            name = CodeProperties.GetClassName(md.DeclaringType) + "." + name;
            if (!htMethodTopics.ContainsKey(name))
                htMethodTopics[name] = new ArrayList();
            string typeName = change + "$" + CodeProperties.GetClassName(tr);
            ((ArrayList)htMethodTopics[name]).Add(typeName);
        }
        private void Emit(MethodReference mr, MethodDefinition md, string change)
        {
            string name = CodeProperties.GetMethodNameWithParametersTight(md, true);
            name = CodeProperties.GetClassName(md.DeclaringType) + "." + name;
            if (!htMethodTopics.ContainsKey(name))
                htMethodTopics[name] = new ArrayList();
            string refName = CodeProperties.GetClassName(mr.DeclaringType) + "." + CodeProperties.GetMethodNameWithParametersTight(mr, true);
            ((ArrayList)htMethodTopics[name]).Add(change + "$" + refName);
        }

        private void EmitFlow(MethodDefinition md, string flow)
        {
            string name = CodeProperties.GetMethodNameWithParametersTight(md, true);
            name = CodeProperties.GetClassName(md.DeclaringType) + "." + name;
            if (!htMethodTopics.ContainsKey(name))
                htMethodTopics[name] = new ArrayList();
            ((ArrayList)htMethodTopics[name]).Add(flow);
        }

        private void CompareMethods(EntityVersion<TypeDefinition> version)
        {
            Hashtable htA = CollectMethods(version.Original);
            Hashtable htB = CollectMethods(version.Target);

            Hashtable deletedTypes = new Hashtable();
            Hashtable commonTypes = new Hashtable();
            Hashtable newTypes = new Hashtable();

            Name<MethodDefinition> nameFunc = delegate(MethodDefinition md) 
            {  return CodeProperties.GetMethodNameWithParametersTight(md, true); };

            CollectCandidateMatches(htA, htB, deletedTypes, commonTypes, newTypes, nameFunc);

            foreach (EntityVersion<MethodDefinition> mVersion in commonTypes.Values)
            {
                string a = EncodeString(mVersion.Original);
                string b = EncodeString(mVersion.Target);
                //Diff.Item[] it = Diff.DiffText(a, b);
                //if (it.Length == 0)
                //    continue;
                if (a == b)
                    continue;

                Emit(mVersion.Original,"changed");

                EmitFlows(mVersion);
                //EmitTypeActivity(mVersion);
                //EmitMethodActivity(mVersion);
            }

            foreach (MethodDefinition md in deletedTypes.Values)
            {
                Emit(md, "removed");
            }

            foreach (MethodDefinition md in newTypes.Values)
            {
                Emit(md,"new");
                if (md.Body == null)
                    continue;
                Name<Flow> nameFlowFunc = delegate(Flow flow)
                {
                    return FormatEntity.Encode(flow.MD, flow.Start) + "->" +
                           FormatEntity.Encode(flow.MD, flow.End);
                };
                Name<ControlFlow> nameCFlowFunc = delegate(ControlFlow flow)
                {
                    return flow.Encode();
                };
                FlowTuple fTuple = CollectFlowsInBody(md);
                foreach (Flow flow in fTuple.flows.Values)
                {
                    EmitFlow(md, "+$" + nameFlowFunc(flow));
                }
                foreach (ControlFlow cflow in fTuple.controlFlows.Values)
                {
                    EmitFlow(md, "+$" + nameCFlowFunc(cflow));
                }
                //foreach (StatementFlow stflow in fTuple.statementFlows.Values)
                //{
                //    EmitFlow(md, "+$" + "Statement->"+FormatEntity.Encode(stflow.Statement));
                //}
            }
        }
        private void EmitFlows(EntityVersion<MethodDefinition> mVersion)
        {
            FlowTuple tupleA = CollectFlowsInBody(mVersion.Original);
            FlowTuple tupleB = CollectFlowsInBody(mVersion.Target);

            Name<Flow> nameFlowFunc = delegate(Flow flow)
            {
                    return FormatEntity.Encode(flow.MD, flow.Start) + "->" +
                           FormatEntity.Encode(flow.MD, flow.End);
            };
            Name<ControlFlow> nameCFlowFunc = delegate(ControlFlow flow)
            {
                return flow.Encode();
            };

            DiffFlows(mVersion, tupleA.flows, tupleB.flows, nameFlowFunc);
            DiffFlows(mVersion, tupleA.controlFlows, tupleB.controlFlows, nameCFlowFunc);
        }


        private void EmitTypeActivity(EntityVersion<MethodDefinition> mVersion)
        {
            // The type activity in a method body.
            Hashtable htATypes = CollectTypesInBody(mVersion.Original);
            Hashtable htBTypes = CollectTypesInBody(mVersion.Target);

            DiffTypeInMethods(mVersion, htATypes, htBTypes);
        }
        private void EmitMethodActivity(EntityVersion<MethodDefinition> mVersion)
        {
            // The type activity in a method body.
            Hashtable htATypes = CollectMethodsInBody(mVersion.Original);
            Hashtable htBTypes = CollectMethodsInBody(mVersion.Target);

            DiffMethodInMethods(mVersion, htATypes, htBTypes);
        }

        private void DiffMethodInMethods(EntityVersion<MethodDefinition> mVersion, Hashtable htATypes, Hashtable htBTypes)
        {
            Hashtable htTypeDeleted = new Hashtable();
            Hashtable htTypeCommon = new Hashtable();
            Hashtable htTypeNew = new Hashtable();

            Name<MethodReference> nameTypeFunc = delegate(MethodReference mr)
                { return CodeProperties.GetMethodNameWithParametersTight(mr,true); };

            CollectCandidateMatches(htATypes, htBTypes, htTypeDeleted, htTypeCommon, htTypeNew, nameTypeFunc);

            foreach (MethodReference mr in htTypeNew.Values)
            {
                Emit(mr, mVersion.Target, "+");
            }

            foreach (MethodReference mr in htTypeDeleted.Values)
            {
                Emit(mr, mVersion.Target, "-");
            }
        }

        private void DiffTypeInMethods(EntityVersion<MethodDefinition> mVersion, Hashtable htATypes, Hashtable htBTypes)
        {
            Hashtable htTypeDeleted = new Hashtable();
            Hashtable htTypeCommon = new Hashtable();
            Hashtable htTypeNew = new Hashtable();

            Name<TypeReference> nameTypeFunc = delegate(TypeReference tr)
                { return CodeProperties.GetClassName(tr); };

            CollectCandidateMatches(htATypes, htBTypes, htTypeDeleted, htTypeCommon, htTypeNew, nameTypeFunc);
            // Focusing on new ones for now.
            // For changed, would really require examining opcodes in poptree.
            foreach (TypeReference tr in htTypeNew.Values)
            {
                Emit(tr, mVersion.Target, "+");
            }
            foreach (TypeReference tr in htTypeDeleted.Values)
            {
                Emit(tr, mVersion.Target, "-");
            }
        }


        private void DiffFlows<T>(EntityVersion<MethodDefinition> mVersion, Hashtable htATypes, Hashtable htBTypes, Name<T> nameTypeFunc )
        {
            Hashtable htTypeDeleted = new Hashtable();
            Hashtable htTypeCommon = new Hashtable();
            Hashtable htTypeNew = new Hashtable();

            CollectCandidateMatches(htATypes, htBTypes, htTypeDeleted, htTypeCommon, htTypeNew, nameTypeFunc);

            //foreach (EntityVersion<T> flow in htTypeCommon.Values)
            //{
            //    string flowvalue = nameTypeFunc(flow.Original);
            //    EmitFlow(mVersion.Target, "*$" + flowvalue);
            //}

            foreach (T flow in htTypeNew.Values)
            {
                string flowvalue = nameTypeFunc(flow);
                EmitFlow(mVersion.Target, "+$"+flowvalue);
            }
            foreach (T flow in htTypeDeleted.Values)
            {
                string flowvalue = nameTypeFunc(flow);
                EmitFlow(mVersion.Target, "-$" + flowvalue);
            }
        }

        public class FlowTuple
        {
            public Hashtable flows=new Hashtable();
            public Hashtable controlFlows=new Hashtable();
            public Hashtable statementFlows = new Hashtable();
        }

        private FlowTuple CollectFlowsInBody( MethodDefinition md )
        {
            FlowTuple tuple = new FlowTuple();
            if (md.Body == null)
                return tuple;
            CILFlowGraph flowgraph = CILMethodBlockConstructor.ConstructFlow(md);
            Hashtable blockPopTreeListMap = new Hashtable();
            foreach (CILInstructionBlock block in flowgraph.Blocks)
            {
                PopTreeList treeList = PopTreeListBuilder.Construct(md,block);
                blockPopTreeListMap[block] = treeList;
            }
            foreach (CILInstructionBlock block in flowgraph.Blocks)
            {
                PopTreeList treeList = (PopTreeList)blockPopTreeListMap[block];
                foreach (PopTree tree in treeList.PopTrees)
                {
                    ArrayList flows = FlowBuilder.ConstructFlow(md, tree); 
                    foreach (Flow flow in flows)
                    {
                        string name = FormatEntity.Encode(md,flow.Start) + "->" +
                                      FormatEntity.Encode(md,flow.End);
                        tuple.flows[name] = flow;
                    }
                    //string stname = FormatEntity.Encode(tree._instruction.Operand);
                    //if (stname != null)
                    //{
                    //    if( stname.StartsWith("Variable") || 
                    //        stname.StartsWith("Field") ||
                    //        stname.StartsWith("Type")||
                    //        stname.StartsWith("Parameter"))
                    //    {

                    //    }
                    //    else
                    //    {
                    //        tuple.statementFlows[stname] = new StatementFlow(tree._instruction);
                    //    }
                    //}
                }
            }

            foreach (ControlFlow cflow in FlowBuilder.BlockFlow(md, flowgraph, blockPopTreeListMap))
            {
                tuple.controlFlows[cflow.Encode()] = cflow;
            }
            
            return tuple;
        }

        //static Random m_random = new Random();
        //static int num = 0;
        private Hashtable CollectTypesInBody( MethodDefinition md )
        {
            Hashtable ht = new Hashtable();

            //CILFlowGraph flowgraph = CILMethodBlockConstructor.ConstructFlow(md);
            //if (m_random.Next() % 301 == 0)
            //{
            //    string name = CodeProperties.GetClassName(md.DeclaringType) + "." + CodeProperties.GetMethodNameWithParametersTight(md, true);
            //    System.IO.StreamWriter sw = new System.IO.StreamWriter("dot/" + num++ +".dot" );
            //    sw.WriteLine("//" + name);
            //    sw.WriteLine("//" + md.DeclaringType.Module.Image.FileInformation.FullName);
            //    sw.Write(flowgraph.ToDot());
            //    sw.Close();
            //}

            foreach (Instruction i in md.Body.Instructions)
            {
                if( i.Operand is TypeDefinition )
                {
                    TypeDefinition type = (TypeDefinition)i.Operand;
                    string name = CodeProperties.GetClassName(type);
                    ht[name] = type;
                }
                else if (i.Operand is TypeReference)
                {
                    TypeReference typeRef = (TypeReference)i.Operand;
                    string name = CodeProperties.GetClassName(typeRef);
                    ht[name] = typeRef;
                }
            }

            return ht;
        }

        private Hashtable CollectMethodsInBody(MethodDefinition md)
        {
            Hashtable ht = new Hashtable();
            foreach (Instruction i in md.Body.Instructions)
            {
                if (i.Operand is MethodReference)
                {
                    MethodReference rf = (MethodReference)i.Operand;
                    string name = CodeProperties.GetMethodNameWithParametersTight(rf, true);
                    ht[name] = rf;
                }
            }
            return ht;
        }

        public string GetStr(string a, int index)
        {
            // REFACTOR ME
            string[] lines = a.Split(new char[] { '\n' });
            return lines[index];
        }

        public string TestHelper(string a, string b, Diff.Item[] f)
        {
            StringBuilder ret = new StringBuilder();
            for (int n = 0; n < f.Length; n++)
            {
                //ret.Append(f[n].deletedA.ToString() + "." + f[n].insertedB.ToString() + "." + f[n].StartA.ToString() + "." + f[n].StartB.ToString() + "*");
                if (f[n].deletedA > 0 && f[n].insertedB == 0)
                {
                    ret.Append("[deleted] ");
                    ret.Append(GetStr(a,f[n].StartA));
                }
                else if (f[n].deletedA > 0 && f[n].insertedB > 0)
                {
                    ret.Append("[changed] ");
                    ret.Append(GetStr(b, f[n].StartB));
                }
                else if (f[n].deletedA == 0 && f[n].insertedB > 0)
                {
                    ret.Append("[added] ");
                    ret.Append(GetStr(b, f[n].StartB));
                }
            }
            // Debug.Write(5, "TestHelper", ret.ToString());
            return (ret.ToString());
        }

        private string EncodeString(MethodDefinition md)
        {
            if (md.Body == null)
                return "";
            StringBuilder code = new StringBuilder();
            foreach (Instruction i in md.Body.Instructions)
            {
                code.Append(i.OpCode.Name);
                code.Append(" ");
                code.Append(i.Operand is String ? String.Format("\"{0}\"", i.Operand) : i.Operand);
                code.Append("\n");
            }
            return code.ToString();
        }


        private Hashtable CollectMethods(TypeDefinition td)
        {
            Hashtable ht = new Hashtable();
            foreach (MethodDefinition md in td.Methods)
            {
                string name = CodeProperties.GetMethodNameWithParametersTight( md, true );
                ht[name] = md;
            }
            return ht;   
        }

        private void CollectCandidateMatches<T>(Hashtable htA, Hashtable htB, 
            Hashtable deletedTypes, Hashtable commonTypes, Hashtable newTypes, 
            Name<T> nameFunc)
        {
            foreach (T m in htB.Values)
            {
                string name = nameFunc(m);
                if (htA.ContainsKey(name))
                {
                    commonTypes[name] = new EntityVersion<T>((T)htA[name], m);
                }
                else
                {   // This is a new entity.
                    newTypes[name] = m;
                }
            }

            foreach (T m in htA.Values)
            {
                string name = nameFunc(m);
                if (!commonTypes.ContainsKey(name))
                {   // This was deleted entity.
                    deletedTypes[name] = htA[name];
                }
            }
        }

        private Hashtable CollectTypes(AssemblyDefinition assem)
        {
            Hashtable ht = new Hashtable();
            foreach (ModuleDefinition md in assem.Modules)
            {
                foreach (TypeDefinition td in md.Types)
                {
                    CollectTypes(td,ht);
                    // Can collect over interfaces.
                    //td.Interfaces
                }
            }
            return ht;
        }

        private void CollectTypes(TypeDefinition td, Hashtable ht)
        {
            ht[td.FullName] = td;
            foreach (TypeDefinition inner in td.NestedTypes)
            {
                CollectTypes(inner,ht);
            }
        }

        public AssemblyDefinition LoadFile(string fileName)
        {
            try
            {
                return AssemblyFactory.GetAssembly(fileName);
            }
            catch
            {
                return null ;
            }
        }

        public void ScanTypes(TypeDefinition td)
        {
            foreach (TypeDefinition inner in td.NestedTypes)
            {
                ScanTypes(inner);
            }
        }

        public void ScanMethodBody(MethodDefinition methd)
        {
            if (methd.Body == null)
            {
                return;
            }

            foreach (Mono.Cecil.Cil.Instruction i in methd.Body.Instructions)
            {
            }
        }
    }
}
